home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-01 / cljul90.zip / SPAWN.C < prev   
Text File  |  1990-06-19  |  31KB  |  646 lines

  1. UNIX Process Control
  2. by Lyle Frost
  3.  
  4.      Process control is an important element of programming on multitasking
  5. systems.  It includes operations such as process creation, termination, and
  6. synchronization.  UNIX is a multiuser as well as multitasking operating system,
  7. and this topic is particularly important in the development of multiuser
  8. applications.
  9.      UNIX operating system services such as process control are accessed using
  10. system calls.  System calls are functions within the operating system kernel
  11. (the heart of the system) which have been made accessible to the application
  12. programmer.  An understanding of how to use the relevant system calls is
  13. critical to being able to control UNIX processes.
  14.  
  15. UNIX Processes
  16.      A process is an instance of execution of a program.  Processes should not
  17. be confused with programs, which are the files executed by processes.  On a
  18. multitasking system, more than one process may be executing the same program
  19. concurrently, and each process may transform itself to execute a different
  20. program.
  21.      Every process on a UNIX system has a unique identifier called the process
  22. ID (PID).  A PID is a positive integer assigned by the operating system to each
  23. process when it is created.  A process may obtain its PID using the getpid
  24. system call, int getpid(), which returns the PID of the calling process.  Since
  25. the PID is used to uniquely identify a process, it may not be changed, though
  26. it may be reused when the process no longer exists.
  27.      Each new process on a UNIX system is created by a previously existing
  28. process, producing a parent-child relationship.  A process may obtain the PID
  29. of its parent using the getppid system call, int getppid(), which returns the
  30. PID of the parent of the calling process.  A process can not change its parent
  31. PID.
  32.      As a result of the parent-child relationship, processes possess a
  33. tree-structured hierarchy.  This is referred to as the process tree.  On every
  34. UNIX system, the root of the process tree is a special process called the
  35. swapper.  The swapper is created with a PID of 0 when the system is booted.  It
  36. manages the allocation of memory for processes and influences the allocation of
  37. the CPU.  The first child created by the swapper (by executing the file
  38. /etc/init) is the process dispatcher, which is assigned a PID of 1.  All
  39. processes initiated by users are descendants of the process dispatcher.  All
  40. other children of the swapper are special system processes which execute code
  41. entirely within the operating system kernel.  Both the swapper and the process
  42. dispatcher exist for the lifetime of the system.
  43.      UNIX keeps track of processes in an internal data structure called the
  44. process table, which has an single entry for every process on the system.  A
  45. listing of the process table can be obtained using the ps command.  Combining
  46. the -e (every process) and -f (full listing) options will cause ps to display
  47. all relevant information for every process in the table.
  48.  
  49. Process Creation
  50.      New processes are created using the fork system call, int fork(), which
  51. creates a new process that is the child of the calling process.  The child
  52. process is an almost exact duplicate of the parent, and although it did not
  53. exist prior to the call to fork, it "resumes" execution of the same program at
  54. the same place as does its parent (i.e., at the return of the call to fork).
  55. The value returned by fork is the simplest way to distinguish the parent from
  56. the child; the PID of the child process is returned to the parent, while a
  57. value of 0 is returned to the child.  On error, a value of -1 is returned to
  58. the parent and no new process is created.  The following C code fragment
  59. outlines the use of fork.
  60.  
  61. int pid;
  62. pid = fork();
  63. switch (pid) {
  64. case -1:     /* error */
  65.   /* error handling */
  66.   break;
  67. case 0:      /* child process */
  68.   /* code executed by child */
  69.   break;
  70. default:     /* parent process */
  71.   /* code executed by parent (pid == PID of child) */
  72.   break;
  73. };
  74.  
  75.      Included in the duplication of the parent process is its file descriptor
  76. table.  A file descriptor is an integer value used by a process to reference an
  77. open file.  This integer value is used by system calls as an index into the
  78. file descriptor table of the process which has the file open.  Since the file
  79. descriptor table is duplicated, all files open in the parent at the time fork
  80. is called will be open in the child.
  81.      A second data structure called the system file table is also used in the
  82. control of open files.  The file pointer (current file position) and access
  83. mode (e.g., read-only) are both stored in this table.  When a file is opened,
  84. an unused entry in the system file table is allocated.  An unused entry in the
  85. file descriptor table is then allocated and linked to that system file table
  86. entry. While each process has its own file descriptor table, the system file
  87. table is global and is not duplicated when a new process is created.  Hence,
  88. inherited file descriptors, which are otherwise independent, share the same
  89. system file table entry and hence the same file pointer and access mode.  Note
  90. that this is the same relationship produced by the dup system call between file
  91. descriptors of the same process.  Attention should be paid to the possibility
  92. of processes interfering with each other by modifying the file pointer or
  93. access mode linked to an inherited file descriptor, since this can be an
  94. elusive source of problems if allowed to occur inadvertently.
  95.      A second caveat exists when file access is buffered, as is the case when
  96. using the C stdio library.  When data is written to a file using stdio, it is
  97. stored in a buffer until either the buffer is full, an explicit call is made to
  98. flush the buffer, or the file is closed, at which time write (the system call
  99. to write data to a file) is called to actually write the buffer contents to the
  100. file.  But since the buffers are part of the process, they are duplicated when
  101. the process is duplicated.  This can produce unexpected results if there is
  102. buffered data waiting to be written when fork is called.  Examining the program
  103. in Figure 1, fputs is called by the original process to write a single line of
  104. text to a file.  After the call to fork both the child and the parent close the
  105. file, causing the buffer contents of each process to be flushed.  As a result,
  106. the data is written to the file twice.
  107.  
  108. Process Transformation
  109.      Processes are generally created with the intention of running a new
  110. program.  This is done with the exec system call.  exec transforms a process by
  111. invoking a new program to replace the one from which the call to exec was
  112. executed.  The new program is executed from its beginning.  There can be no
  113. return from a successful call to exec because the code containing the call has
  114. been replaced with that of the new program.  A call to exec usually, but not
  115. necessarily, closely follows a call to fork.
  116.      exec is actually a generic name for a group of six system calls.  The
  117. archetypal exec function is execve, which is called with the following
  118. arguments:
  119.  
  120. int execve(char *path, char *argv[], char *envp[]);
  121.  
  122. path is the path name (i.e., a file name with a path prefix giving its
  123. location) of the program to execute.  argv (argument vector) is an array of
  124. strings which are the arguments to pass to the new program.  envp (environment
  125. pointer) is an array of strings of the form "name=value" (e.g., "TERM=VT100")
  126. defining the environment for the new program.  The environment normally
  127. contains such information as the user's home directory and the terminal type.
  128. Both argv and envp must be terminated with the NULL pointer.
  129.      The five other forms of the exec system call are basically the same as
  130. execve, but with slightly reorganized parameters.  The complete list is given
  131. below.
  132.  
  133. int execl(path, arg0, arg1, ..., argn, NULL)
  134. int execle(path, arg0, arg1, ..., argn, NULL, envp)
  135. int execlp(file, arg0, arg1, ..., argn, NULL)
  136. int execv(path, argv)
  137. int execve(path, argv, envp)
  138. int execvp(file, argv) char *path, *file, *argn, *argv[], *envp[];
  139.  
  140.     Notice first that these functions divide into two groups which differ only
  141. by replacing the argument vector argv by an expanded argument list arg0, arg1,
  142. ..., argn, NULL.  The functions are named accordingly using v or l to indicate
  143. a vector or a list.  Secondly, the functions ending with a p will take the file
  144. name while the others require the path name.  The former search the path
  145. defined in the environment of the calling process (e.g., "PATH=:/bin:/usr/bin")
  146. to find the file.  Lastly, the functions ending in e allow a new environment to
  147. be passed.  The others copy the current environment to the new program.
  148.      Whether vector or list, all six exec functions require that the program
  149. name and its arguments be passed as individual tokens.  But when this data is
  150. obtained from user input or a file, it is normally a single string and so must
  151. be tokenized into either a vector or a list.  Figure 2 shows the source code
  152. for a new exec function, execs, that accepts commands in string form; the code
  153. for the corresponding environment and path functions execse and execsp is
  154. similar.
  155.  
  156. int execs(char *cmdlin);
  157. int execse(char *cmdlin, char *envp[]);
  158. int execsp(char *cmdlin);
  159.  
  160.     The execs functions tokenize the string cmdlin to create an argument vector
  161. which is then used to call one of the execv functions.  The tokenizing is done
  162. with a function called cmdtok (the source for this is not shown).  cmdtok is
  163. similar to the ANSI C function strtok, but treats contiguous white space
  164. between tokens as a single space and recognizes the metacharacters \, ', ", and
  165. #.  The function of each of these metacharacters is the same as for the shell
  166. (see section 3.2 of KERN84).
  167.      An alternative to the execs functions is to invoke the shell with one of
  168. the provided exec functions and let it do the tokenization.  The string
  169. containing the command line to be executed is passed as an argument to the
  170. shell as shown below.
  171.  
  172. execl("/bin/sh", "sh", "-c", cmdlin, NULL);
  173.  
  174.     -c instructs sh to execute the command line in the next argument (cmdlin)
  175. and then terminate; the ANSI C library function system executes a command line
  176. in this way.  sh expands any metacharacters in cmdlin and constructs an
  177. argument vector.  The new shell calls fork to create a new process which calls
  178. exec with the argument vector.  The advantage to this method is that the full
  179. functionality of the shell is available in processing the command line (of
  180. course, additional metacharacters can always be implemented in execs if
  181. needed).  One drawback is that an extra process has been created.  Also, the
  182. original process knows the PID of the new shell, but not of the process
  183. actually executing the command.
  184.      Files remain open across a call to exec.  This is convenient for setting
  185. up a program's standard input, output, and error.  Any unnecessary files,
  186. however, should be closed before executing a new program.  The fcntl system
  187. call can be used to set a file desciptor's close-on-exec flag, causing it to be
  188. closed automatically when exec is called.
  189.      A C program may access its argument list through the second parameter of
  190. the function main; main is the entry point of every C program.
  191.  
  192. int main(int argc, char *argv[]);
  193.  
  194.     argc is set to the number of elements (excluding the terminating NULL) of
  195. argv.  By convention, argv[0] is the program name (i.e., the file argument in
  196. execlp and execvp).  This is not enforced by exec, but is often required by the
  197. program being executed.
  198.      The environment may be accessed through the global pointer
  199. environ: extern char **environ. Most UNIX C implementations allow main a third
  200. parameter char *envp[] for the environment passed to exec, but since this
  201. deviates from the ANSI C standard it is more portable to access the environment
  202. using environ.  Also, environ is the actual environment pointer, while envp
  203. simply holds a copy of the environment pointer at the time the program began
  204. execution.  When the environment is modified using the library function putenv,
  205. the location of the environment may be changed, in which case the address
  206. stored in envp will no longer be valid.
  207.  
  208. Process Termination
  209.      A process terminates using the exit system call, void exit(int status).
  210. status is a value to be reported back to the parent of the terminating process.
  211. While any interpretation may be placed on the value of status (as long as the
  212. parent and child are consistent), convention dictates that a value of 0 be used
  213. to indicate a successful completion and non-zero values for failure; the
  214. shell's if statement is designed for programs using this convention.
  215.      Before terminating the calling process, exit automatically calls fclose
  216. for all open streams.  This is important because buffered data would be lost
  217. otherwise.  close is called for any open file descriptors not associated with a
  218. stdio stream.  If a process terminates while it still has children, the parent
  219. PID of each of the children is changed to 1.  Or in other words, orphan
  220. processes are adopted by the process dispatcher.  As the final step, exit sends
  221. a "death of child" signal (SIGCLD) to its parent.
  222.      Even after a process has terminated, it still has its entry in the process
  223. table.  A process that has terminated but has not yet been removed from the
  224. process table is called a zombie. Zombies can be identified from the ps command
  225. listing by <defunct> (or something similar) in the COMMAND column.  A process
  226. can only be removed from the process table by its parent.  The mechanism for
  227. this will be described in the next section.
  228.      Unlike most system calls, exit is included in the ANSI C standard library,
  229. and the macros EXIT_SUCCESS and EXIT_FAILURE are defined in <stdlib.h> for use
  230. as the argument to exit when only a simple success or failure status is
  231. required.  ANSI C also specifies that using return from the function main is
  232. equivalent to calling exit with status equal to the value returned.  In many
  233. non-ANSI implementations, however, the return value from main is discarded, so
  234. it is best to use exit.
  235.      The only way that a process can terminate without explicitly calling exit
  236. is by receiving a signal whose handler function is set to SIG_DFL (default
  237. action).  When this occurs, exit is called automatically with the number of the
  238. received signal as the status argument.
  239.  
  240. Awaiting Process Termination
  241.      A process may await the termination of a child using the wait system call.
  242. wait allows a process to synchronize its execution with the termination of a
  243. child.  When a process calls wait, the operating system first checks to see if
  244. that process has any existing children.  If it does not, wait returns a value
  245. of -1 with errno set to ECHILD.  If the process has a child which has
  246. terminated (i.e., is a zombie), the PID of that child is returned by wait and
  247. it is removed from the process table.  If wait is called by a process with
  248. children but none of them have terminated, the calling process suspends
  249. execution until a signal is received.  If a SIGCLD signal is received, the
  250. process will wake up and wait will check the process table for zombie children
  251. again.
  252.      The information returned in the integer pointed to by statusp depends upon
  253. how the child terminated.  If it terminated by calling exit, bits 0 to 6 of the
  254. integer will be set to zero and bits 8 to 15 will contain the status argument
  255. passed to exit.  If the child terminated due to a signal, bits 0 to 6 will
  256. contain the number of the signal and bits 8 to 15 will be 0.  Some signals
  257. cause a core image to be produced for debugging purposes, in which case bit 7
  258. will be set.  When using process tracing, a process may be stopped by a signal.
  259. Here, bits 0 to 6 of the integer will all be set to one and bits 8 to 15 will
  260. contain the number of the signal.  For more information on tracing see section
  261. 11.1 of BACH86.  The code fragment below outlines the use of wait.  Some macros
  262. are defined to make calls to wait more readable.
  263.  
  264. #define WT_MASK    (0x7F)
  265. #define WT_EXITED  (0x00)
  266. #define WT_STOPPED (0x7F)
  267. #define WT_CORE    (0x80)
  268. #define WT_BITS    (8)
  269.  
  270. int pid, status, sig;
  271. pid = wait(&status);
  272. if (pid == -1) {
  273.   if (errno == ECHILD) {
  274.     /* process has no existing children */
  275.   } else {
  276.     /* error */
  277.   }
  278. } else {
  279.   /* pid == PID of the child */
  280.   switch (status & WT_MASK) {
  281.   case WT_EXITED:   /* child terminated by calling exit */
  282.     status >>= WT_BITS; /* value passed to exit */
  283.     break;
  284.   case WT_STOPPED:  /* child stopped due to a signal */
  285.     sig = status >> WT_BITS;
  286.     break;
  287.   default:          /* child terminated due to a signal */
  288.     sig = status & WT_MASK;
  289.     if (status & WT_CORE) {
  290.       /* core image was produced */
  291.     }
  292.   break;
  293.   }
  294. }
  295.  
  296.      It is often desired for a process to wait for the termination of a
  297. specific child, usually immediately after the call to fork that creates it.
  298. This can be accomplished using the following construct.
  299.  
  300. int pid, status;
  301. while (wait(&status) != pid)
  302.   ;
  303.  
  304.      A process may await the termination of all its children by setting SIGCLD
  305. to be ignored before calling wait.  wait will still remove zombie children from
  306. the process table when SIGCLD is received, but it will not return until all
  307. children have terminated.  When there are no more unwaited-for children, wait
  308. will return a value of -1 and with errno set to ECHILD.
  309.  
  310. int status;
  311. signal(SIGCLD, SIG_IGN);
  312. wait(&status);
  313. if (errno != ECHILD) {
  314.   /* error */
  315. }
  316.  
  317.      zombie children may remain in the process table indefinitely. A parent
  318. could create a child then enter a loop, never calling either wait or exit.  The
  319. process dispatcher calls wait regularly to prevent orphan zombies from
  320. cluttering up the process table.
  321.  
  322. Process Groups
  323.      In addition to the process ID used to identify individual processes, each
  324. process also has a process group ID (PGID) which is used to identify groups of
  325. processes.  The PGID is inherited by a child from its parent.  Unlike the PID,
  326. a process can change its PGID, but only by creating a new group.  This is done
  327. using the setpgrp system call.
  328.  
  329. int setpgrp();
  330.  
  331. setpgrp sets the PGID of the calling process to the same value as its PID,
  332. returning the new PGID.  Because the process which calls setpgrp is the first
  333. member of the new group and only its descendants may belong to that group (by
  334. inheriting its PGID), it is referred to as the process group leader.
  335.      A process can determine its PGID using the getpgrp system call.
  336.  
  337. int getpgrp();
  338.  
  339. getpgrp returns the PGID of the calling process.  Because the PID of the group
  340. leader is the same as the PGID, getpgrp also identifies the group leader.
  341.      Since only descendants of a process group leader can be members of that
  342. process group, there is a direct correlation between process groups and the
  343. process tree.  Each process group leader is the root of a subtree which, after
  344. the subtrees of descendant group leaders have been pruned, contains only
  345. processes belonging to that group.  If no members of the group have terminated
  346. leaving children which have been adopted by the process dispatcher, this
  347. subtree contains all the processes in that group.
  348.      A process can be associated with a terminal, which is called the control
  349. terminal for that process.  The control terminal is inherited from the parent
  350. when a new process is created.  A process is disassociated from its control
  351. terminal when it calls setpgrp, becoming a process group leader (setpgrp does
  352. not close the terminal).  Also, only a process group leader can establish a
  353. control terminal, becoming the control process for that terminal. This is done
  354. automatically the first time it opens a terminal after calling setpgrp.
  355. Neither opening subsequent terminals nor closing the control terminal will
  356. affect this association.  The standard input, output, and error are not
  357. necessarily directed to the control terminal.
  358.      When a process group leader terminates, the PGIDs of all members of the
  359. group are set to 0.  If the group leader is assocated with a control terminal,
  360. a hangup signal (SIGHUP) is sent to all members of the group, causing them to
  361. terminate if the SIGHUP handler is set to SIG_DFL.  This is how shell
  362. background processes are terminated when a user logs out.
  363.      A process that is not associated with a control terminal is called a
  364. daemon.  The printer spooler is an example of a such a process.  Daemons can be
  365. identified from the ps command listing by a ? in the TTY column.  Note that
  366. running a process in the background from the shell using & does not make it a
  367. daemon.  Normally the shell waits for a child to terminate, but when a process
  368. is run in the background it simply skips the wait and returns the prompt.
  369. There is no call to setpgrp and so the control terminal is not disconnected.
  370.  
  371. The appinit Program
  372.      Multiuser applications execute as multiple processes, one for each
  373. terminal from which the application may be accessed.  Usually there are also
  374. some daemon processes which perform administrative functions.  One way to start
  375. up such an application is to run the daemons manually, then go to each terminal
  376. and run the appropriate interactive program for that location.  On a large
  377. system, however, there can be a large number of terminals located at great
  378. distances from each other.
  379.      The appinit program is designed using the system calls discussed above to
  380. automate the procedure for the startup of a multiuser application.  It is a
  381. process dispatcher similar to /etc/init but for use with application software.
  382. Like /etc/init, appinit reads the process specifications from a control file.
  383. Entries in this control file have the format tty command.
  384.     tty names the control terminal for the process to be created.  If this
  385. field does not begin with a /, the prefix "/dev/" is automatically added.  A
  386. daemon may be created by specifying a control terminal of /dev/null.  command
  387. is a command line specifying the program to be executed.
  388.      The source code for appinit is shown in Figure 3.  There are two major
  389. loops.  The first is the process creation loop.  Here fork is called to create
  390. each process.  Following the call to fork the new child then calls setpgrp to
  391. become a process group leader and disconnect itself from the parent's control
  392. terminal; since each child process becomes a process group leader, appinit may
  393. be terminated without a SIGHUP signal being sent to terminate the dispatched
  394. processes.  The standard input, output, and error are then redirected to the
  395. new control terminal, after which ioctl is used to set the parameters (device
  396. I/O options; see ATT88b) for the new control terminal to the values saved from
  397. the old one.  execsp is then called to execute the command read from the
  398. control file, allowing the use of the metacharacters \, ', ", and #.
  399.      In the second loop, appinit monitors the processes it has created.  It
  400. does this by calling wait repeatedly until all its children have terminated.
  401. Details on the termination of each process is displayed.
  402.      When using appinit, the control terminals for the processes to be created
  403. must be disabled.  This is good for systems where security is important,
  404. because if an application program terminates for some reason, all that will be
  405. left is a dead terminal.
  406.      This version of appinit is a lean implementation.  One useful enhancement
  407. would be to allow processes to be terminated and new ones created on command.
  408. Also, /etc/init will automatically respawn specified processes if they
  409. terminate, and this would be a useful feature for appinit.
  410.  
  411. References
  412.     BACH86 Bach, M. The Design of the UNIX Operating System. Englewood Cliffs,
  413. NJ: Prentice Hall, 1986.
  414.     BOUR83 Bourne, S. The UNIX System. Reading, MA: Addison-Wesley, 1983.
  415.     KERN84 Kernighan, B., and R. Pike. The UNIX Programming Environment.
  416. Englewood Cliffs, NJ: Prentice Hall, 1984.
  417.     ATT88a AT&T. UNIX System V Programmer's Reference Manual. Englewood Cliffs,
  418. NJ: Prentice Hall, 1988.
  419.     ATT88b AT&T. UNIX System V User's Reference Manual. Englewood Cliffs, NJ:
  420. Prentice Hall, 1988.
  421.  
  422.  
  423. Figure 1. fork and File Buffering
  424.  
  425. #include <stdio.h>
  426. #include <stdlib.h>
  427.  
  428. int fork();
  429.  
  430. int main(int argc, char *argv[])
  431. {
  432.     FILE *fp = NULL;
  433.  
  434.     /* open file and write one line */
  435.     fp = fopen("outfile.txt", "w");
  436.     if (fp == NULL) {
  437.          perror("fopen");
  438.          exit(EXIT_FAILURE);
  439.     }
  440.     fputs("One line written to file.\n", fp);
  441.  
  442.     /* create new process */
  443.     if (fork() == -1) {
  444.          perror("fork");
  445.          exit(EXIT_FAILURE);
  446.     }
  447.  
  448.     /* close file (buffers flushed automatically) */
  449.     fclose(fp);
  450.  
  451.     exit(EXIT_SUCCESS);
  452. }
  453.  
  454. Figure 2. execs Function
  455.  
  456. #include <errno.h>
  457. #include <stdlib.h>
  458. #include <stdio.h>
  459. #include <string.h>
  460. #include "syscalls.h"
  461.  
  462. char *cmdtok(char *s);
  463.  
  464. /* execs:  execute a file (string parameter) */
  465. int execs(const char *cmdlin)
  466. {
  467.     char *tcmdlin = NULL;                  /* tmp command line */
  468.     int argc = 0;                          /* argument count */
  469.     char **argv = NULL;      /* argument vector */
  470.     char *p = NULL;                        /* gp char pointer */
  471.  
  472.     /* make working copy of cmdlin */
  473.     tcmdlin = (char *)calloc((size_t)(strlen(cmdlin) + 1), sizeof(*tcmdlin));
  474.     strcpy(tcmdlin, cmdlin);
  475.  
  476.     /* construct argv array from cmdlin */
  477.     argc = 0;
  478.     argv = NULL;
  479.     p = cmdtok(tcmdlin);
  480.     while (1) {
  481.          /* allocate memory for new argv element */
  482.          argv = (char **)realloc(argv, (size_t)((argc + 1) * sizeof(*argv)));
  483.          if (p == NULL) {
  484.              argv[argc] = NULL;
  485.              break;
  486.          }
  487.          argv[argc] = (char *)calloc((size_t)(strlen(p) + 1),
  488. sizeof(*argv[argc]));
  489.          strcpy(argv[argc++], p);
  490.          p = cmdtok(NULL);
  491.     }
  492.  
  493.     /* execute program */
  494.     execv(argv[0], argv);
  495.  
  496.     /* free memory */
  497.     while (--argc >= 0) {
  498.          free(argv[argc]);
  499.     }
  500.     free(argv);
  501.     free(tcmdlin);
  502.  
  503.     return -1;
  504. }
  505.  
  506. Figure 3. appinit Program
  507.  
  508. #include <errno.h>
  509. #include <fcntl.h>
  510. #include <limits.h>
  511. #include <signal.h>
  512. #include <stdlib.h>
  513. #include <stdio.h>
  514. #include <string.h>
  515. #include <termio.h>
  516. #include "syscalls.h"
  517.  
  518. /* aps:  report appinit process status */
  519. void aps(pidc, pidv, ttyv, cmdlinv)
  520. int pidc;
  521. int pidv[];
  522. char *ttyv[];
  523. char *cmdlinv[];
  524. {
  525.     int i = 0;
  526.  
  527.     printf("TTY    COMMAND                   PID\n");
  528.     for (i = 0; i < pidc; i++) {
  529.          printf("%-6s %-25s", ttyv[i] + 5, cmdlinv[i]);
  530.          if (pidv[i] != 0) {
  531.              printf(" %5d\n", pidv[i]);
  532.          } else {
  533.              printf("<terminated>\n");
  534.          }
  535.     }
  536.  
  537.     return;
  538. }
  539.  
  540. int main(int argc, char *argv[])
  541. {
  542.     struct termio ttyparms;                /* terminal parameters */
  543.     int pidc = 0;                          /* process count */
  544.     int *pidv = NULL;        /* PID vector */
  545.     char **ttyv = NULL;      /* tty device path name vector */
  546.     char **cmdlinv = NULL;                 /* command line vector */
  547.     int pid = 0;             /* PID */
  548.     int status = 0;                        /* wait status */
  549.     int i = 0;               /* gp loop index */
  550.  
  551.     /* read control file, build ttyv and cmdlinv, set pidc, allocate pidv */
  552.     .
  553.     .
  554.     .
  555.  
  556.     /* get control terminal parameter settings */
  557.     ioctl(0, TCGETA, &ttyparms);
  558.  
  559.     /* create child processes */
  560.     for (i = 0; i < pidc; i++) {
  561.          pidv[i] = fork();
  562.          switch (pidv[i]) {
  563.          case -1:       /* error */
  564.              perror("fork");
  565.              exit(EXIT_FAILURE);
  566.              break;
  567.          case 0:        /* child process */
  568.              setpgrp(); /* make process group leader */
  569.              if (strcmp(ttyv[i], "/dev/null") != 0) {
  570.                  /* connect new control terminal */
  571.                  close(0);                 /* stdin */
  572.                  if (open(ttyv[i], O_RDONLY) == -1) {
  573.                         perror("opening stdin");
  574.                         exit(EXIT_FAILURE);
  575.                  }
  576.                  close(1);                 /* stdout */
  577.                  if (open(ttyv[i], O_WRONLY) == -1) {
  578.                         perror("opening stdout");
  579.                         exit(EXIT_FAILURE);
  580.                  }
  581.                  close(2);                 /* stderr */
  582.                  if (open(ttyv[i], O_WRONLY) == -1) {
  583.                         exit(EXIT_FAILURE);
  584.                  }
  585.                  /* set control terminal parameter settings */
  586.                  ioctl(0, TCSETAF, &ttyparms);
  587.              }
  588.              /* execute program */
  589.              execsp(cmdlinv[i]);
  590.              perror("execsp");
  591.              exit(EXIT_FAILURE);
  592.              break;
  593.          default:       /* parent process */
  594.              printf("Process %d running on %s.\n", pidv[i], ttyv[i]);
  595.              continue;
  596.              break;
  597.          }
  598.     }
  599.  
  600.     /* monitor child processes */
  601.     for (;;) {
  602.          putchar('\n');
  603.          aps(pidc, pidv, ttyv, cmdlinv);
  604.          putchar('\n');
  605.          pid = wait(&status);
  606.          if (pid == -1) {
  607.              if (errno == ECHILD) {
  608.                  printf("All child processes terminated.\n");
  609.                  break;
  610.              }
  611.              perror("wait");
  612.              exit(EXIT_FAILURE);
  613.          }
  614.          for (i = 0; i < pidc; i++) {
  615.              if (pidv[i] == pid) {
  616.                  pidv[i] = 0;
  617.                  break;
  618.              }
  619.          }
  620.          if (i >= pidc) {
  621.              continue;  /* unknown child */
  622.          }
  623.          printf("%s: ", ttyv[i]);
  624.          switch (status & WT_MASK) {
  625.          case WT_EXITED:
  626.              printf("Process %d terminated with exit(%d).\n",
  627.                              pid, status >> WT_BITS);
  628.              break;
  629.          case WT_STOPPED:
  630.              printf("Process %d stopped due to signal %d.\n",
  631.                              pid, status >> WT_BITS);
  632.              break;
  633.          default:
  634.              printf("Process %d terminated due to signal %d.",
  635.                              pid, status & WT_MASK);
  636.              if (status & WT_CORE) {
  637.                  printf("  A core image was produced.");
  638.              }
  639.              printf("\n");
  640.              break;
  641.          }
  642.     }
  643.  
  644.     exit(EXIT_SUCCESS);
  645. }
  646.